﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace TaskManager
{
    public class Channel<T>
    {
        private List<T> buffer = new List<T>();
        private object lockPusObj = new object();
        private object lockPopObj = new object();
        private bool isClose = false;

        public UInt32 MaxBufferCount { get; private set; }

        public Channel() : this(1) { }

        public Channel(UInt32 max)
        {
            this.MaxBufferCount = Math.Max(max, 1);
        }

        public int BufferCount
        {
            get
            {
                lock (buffer)
                    return buffer.Count;
            }
        }

        public bool Push(T data)
        {
            Monitor.Enter(lockPusObj);

            while (!isClose && buffer.Count >= MaxBufferCount)
                Monitor.Wait(lockPusObj);

            if (!isClose)
            {
                lock (buffer)
                {
                    buffer.Add(data);
                }
            }

            //Console.WriteLine($"<= {data}");

            Monitor.Exit(lockPusObj);

            lock (lockPopObj)
                Monitor.PulseAll(lockPopObj);

            return !isClose;
        }

        public bool Pop(out T obj)
        {
            Monitor.Enter(lockPopObj);

            while (!isClose && buffer.Count == 0)
                Monitor.Wait(lockPopObj);


            T first = default(T);
            if (!isClose)
            {
                lock (buffer)
                {
                    first = buffer.First();
                    buffer.RemoveAt(0);
                }
            }

            //Console.WriteLine($"=> {first}");

            Monitor.Exit(lockPopObj);

            lock (lockPusObj)
                Monitor.PulseAll(lockPusObj);

            obj = first;
            return !isClose;
        }

        public void Close()
        {
            isClose = true;

            lock (buffer)
            {
                buffer.Clear();
            }

            lock (lockPopObj)
                Monitor.PulseAll(lockPopObj);

            lock (lockPusObj)
                Monitor.PulseAll(lockPusObj);
        }
    }
}
